package com.yj.spring.charpter03;

import cn.hutool.core.util.StrUtil;
import com.yj.spring.common.ArgumentValue;
import com.yj.spring.common.PropertyValue;
import lombok.SneakyThrows;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XmlDefinitionParse {

    private String path;

    List<BeanDefinition> beanDefinitions = new ArrayList<>();
    Map<String, Object> beans = new HashMap<>();

    public XmlDefinitionParse(String path) {
        this.path = path;
    }

    public void parse() {
        if (StrUtil.isBlank(path)) {
            throw new IllegalArgumentException("path is blank");
        }

        URL resource = this.getClass().getClassLoader().getResource(path);
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resource);
            Element rootElement = document.getRootElement();
            List<Element> elements = rootElement.elements();

            for (Element element : elements) {
                String id = element.attribute("id").getValue();
                String className = element.attribute("class").getValue();

                Element constructorElement = element.element("constructor-arg");
                List<ArgumentValue> argumentValues = new ArrayList<>();
                if (constructorElement != null) {
                    String name = constructorElement.attribute("name").getValue();
                    String value = constructorElement.attribute("value").getValue();
                    String type = constructorElement.attribute("type").getValue();

                    ArgumentValue argumentValue = new ArgumentValue(name, value, type);
                    argumentValues.add(argumentValue);
                }
                Element propertyElement = element.element("property");
                List<PropertyValue> propertyValues = new ArrayList<>();
                if (propertyElement != null) {
                    String name = propertyElement.attribute("name").getValue();
                    String value = propertyElement.attribute("value").getValue();
                    String type = propertyElement.attribute("type").getValue();
                    Attribute attribute = propertyElement.attribute("ref");
                    String ref = null;
                    if (attribute != null){
                        ref = attribute.getValue();
                    }
                    PropertyValue propertyValue = new PropertyValue(name, value, type, ref);
                    propertyValues.add(propertyValue);
                }

                BeanDefinition beanDefinition = new BeanDefinition(id, className);
                beanDefinition.setArgumentValues(argumentValues);
                beanDefinition.setPropertyValues(propertyValues);
                beanDefinitions.add(beanDefinition);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void refresh() {
        parse();
        doRefresh();
    }

    @SneakyThrows
    private void doRefresh() {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            Object bean = null;

            // 先通过构造方法实例化对象
            Object[] parameters = new Object[beanDefinition.getArgumentValues().size()];
            Class[] parameterTypes = new Class[beanDefinition.getArgumentValues().size()];

            Class<?> aClass = Class.forName(beanDefinition.getClassName());

            for (int i = 0; i < parameters.length; i++) {
                String type = beanDefinition.getArgumentValues().get(i).getType();
                if (type.equals("int") || type.equals("java.lang.Integer")) {
                    parameterTypes[i] = Integer.class;
                    parameters[i] = Integer.parseInt((String) beanDefinition.getArgumentValues().get(i).getValue());
                } else if (type.equals("string") || type.equals("java.lang.String")) {
                    parameterTypes[i] = String.class;
                    parameters[i] = beanDefinition.getArgumentValues().get(i).getValue();
                } else if (type.equals("boolean") || type.equals("java.lang.Boolean")) {
                    parameterTypes[i] = Boolean.class;
                    parameters[i] = Boolean.parseBoolean((String) beanDefinition.getArgumentValues().get(i).getValue());
                } else {
                    // 默认String类型
                    parameterTypes[i] = String.class;
                    parameters[i] = beanDefinition.getArgumentValues().get(i).getValue();
                }
            }

            bean = aClass.getConstructor(parameterTypes).newInstance(parameters);
            // 再通过setter方法设置属性值
            Object[] propertyValues = new Object[beanDefinition.getPropertyValues().size()];
            Class[] propertyTypes = new Class[1];
            for (int i = 0; i < propertyValues.length; i++) {
                String type = beanDefinition.getPropertyValues().get(i).getType();
                String name = beanDefinition.getPropertyValues().get(i).getName();
                String value = beanDefinition.getPropertyValues().get(i).getValue();
                String ref = beanDefinition.getPropertyValues().get(i).getRef();
                if (ref != null) {
                    propertyValues[i] = beans.get(ref);
                } else {
                    if (type.equals("int") || type.equals("java.lang.Integer")) {
                        propertyTypes[0] = Integer.class;
                    } else if (type.equals("string") || type.equals("java.lang.String")) {
                        propertyTypes[0] = String.class;
                    } else if (type.equals("boolean") || type.equals("java.lang.Boolean")) {
                        propertyTypes[0] = Boolean.class;
                    } else {
                        // 默认String类型
                        propertyTypes[0] = String.class;
                    }
                    propertyValues[i] = value;
                }
                Method method = aClass.getDeclaredMethod("set" + StrUtil.upperFirst(name), propertyTypes);
                method.invoke(bean, propertyValues[i]);
            }
            beans.put(beanDefinition.getId(), bean);
        }
    }
}
